Repository navigation
Harden MCP safety boundaries and streamline authorized messaging - #2
Conversation
Review: request changesThe boundary fixes are correct and well tested, but the PR adds one confirmed receive regression and several changes that work against the streamlined-UX goal. CI is green (Node 22/24, Docker image, fmsg-docker acceptance). I ran two local probes against the branch and Blocking1.
2. Attachment saving is removed with nothing to replace it.
Should fix (UX and agent behaviour)3. "Data, not instructions" framing is now on everything.
4. The injection wording got weaker exactly where it matters.
5. Upstream error text is now unbounded. 6. Browser dev tools are blocked on loopback. 7. README lost a working recipe and contradicts itself.
8. 9. New startup failures are invisible in stdio hosts.
Minor
Checked and fine
🤖 Generated with Claude Code |
A reference implementation of the experimental file-uploads draft at modelcontextprotocol/experimental-ext-file-uploads, branch localden/initial-draft at 6101d34eb52b21d4b87028543ecd1fd59ed3d5df (the head after PR #2's merge). Over HTTP with an https public MCP URL, server/discover advertises the extension with maxInlineBytes, and the attachments of send_message, reply and create_draft also take FileInputs ({id, name, mimeType, size, digest, data?}, marked x-mcp-file), several per call. - Inline files are decoded and checked against their size and digest. - Files without data are checked on their metadata (ids, names, media type, digest form, the operator's per-file limit, the inline budget) after the tool's own checks. The call then creates its fmsg draft as the staging location and answers input_required with one upload target per file. - Targets are PUT /mcp/uploads/{token} with credentials "include": an HMAC token bound to the principal, draft, file, declared size and SHA-256, call and expiry. Each takes one successful upload and streams it into the draft, so no file bytes are kept on this server. - The retry is verified by the SDK codec (MAC, expiry, principal; -32602 on failure), then against the tool, the canonical argument hash and the per-file outcomes. Each file is confirmed in the draft by name, size and digest before the draft is sent (or returned by create_draft). Failed outcomes, early retries, repeated or concurrent retries, expired targets and malformed outcomes get specific errors. The SDK accepts only the base protocol's input request kinds, so a tool returns input_required with requestState alone, and the HTTP layer adds inputRequests.uploads to that response (JSON or SSE). Classic data_base64, data_text and stdio path attachments are unchanged. Tests cover the rules, plus an end-to-end run of a minimal client harness over HTTPS through a TLS proxy and the OAuth path. docs/files-extension-notes.md lists every ambiguity or gap in the draft, the choice made, and a suggested fix, for the working group. Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
A reference implementation of the experimental file-uploads draft at modelcontextprotocol/experimental-ext-file-uploads, branch localden/initial-draft at 6101d34eb52b21d4b87028543ecd1fd59ed3d5df (the head after PR #2's merge). Over HTTP with an https public MCP URL, server/discover advertises the extension with maxInlineBytes, and the attachments of send_message, reply and create_draft also take FileInputs ({id, name, mimeType, size, digest, data?}, marked x-mcp-file), several per call. - Inline files are decoded and checked against their size and digest. - Files without data are checked on their metadata (ids, names, media type, digest form, the operator's per-file limit, the inline budget) after the tool's own checks. The call then creates its fmsg draft as the staging location and answers input_required with one upload target per file. - Targets are PUT /mcp/uploads/{token} with credentials "include": an HMAC token bound to the principal, draft, file, declared size and SHA-256, call and expiry. Each takes one successful upload and streams it into the draft through the same receiver as presigned upload links (streamIntoDraft), so no file bytes are kept on this server. - The retry is verified by the SDK codec (MAC, expiry, principal; -32602 on failure), then against the tool, the canonical argument hash and the per-file outcomes. Each file is confirmed in the draft by name, size and digest before the draft is sent (or returned by create_draft). Failed outcomes, early retries, repeated or concurrent retries, expired targets and malformed outcomes get specific errors. The SDK accepts only the base protocol's input request kinds, so a tool returns input_required with requestState alone, and the HTTP layer adds inputRequests.uploads to that response (JSON or SSE). Classic data_base64, data_text and stdio path attachments are unchanged. Tests cover the rules, plus an end-to-end run of a minimal client harness over HTTPS through a TLS proxy and the OAuth path. docs/files-extension-notes.md lists every ambiguity or gap in the draft, the choice made, and a suggested fix, for the working group. Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
A reference implementation of the experimental file-uploads draft at modelcontextprotocol/experimental-ext-file-uploads, branch localden/initial-draft at 6101d34eb52b21d4b87028543ecd1fd59ed3d5df (the head after PR #2's merge). Over HTTP with an https public MCP URL, server/discover advertises the extension with maxInlineBytes, and the attachments of send_message, reply and create_draft also take FileInputs ({id, name, mimeType, size, digest, data?}, marked x-mcp-file), several per call. - Inline files are decoded and checked against their size and digest. - Files without data are checked on their metadata (ids, names, media type, digest form, the operator's per-file limit, the inline budget) after the tool's own checks. The call then creates its fmsg draft as the staging location and answers input_required with one upload target per file. - Targets are PUT /mcp/uploads/{token} with credentials "include": an HMAC token bound to the principal, draft, file, declared size and SHA-256, call and expiry. Each takes one successful upload and streams it into the draft through the same receiver as presigned upload links (streamIntoDraft), so no file bytes are kept on this server. - The retry is verified by the SDK codec (MAC, expiry, principal; -32602 on failure), then against the tool, the canonical argument hash and the per-file outcomes. Each file is confirmed in the draft by name, size and digest before the draft is sent (or returned by create_draft). Failed outcomes, early retries, repeated or concurrent retries, expired targets and malformed outcomes get specific errors. The SDK accepts only the base protocol's input request kinds, so a tool returns input_required with requestState alone, and the HTTP layer adds inputRequests.uploads to that response (JSON or SSE). Classic data_base64, data_text and stdio path attachments are unchanged. Tests cover the rules, plus an end-to-end run of a minimal client harness over HTTPS through a TLS proxy and the OAuth path. docs/files-extension-notes.md lists every ambiguity or gap in the draft, the choice made, and a suggested fix, for the working group. Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
Attachment downloads could overwrite local files while advertised as read-only, and non-loopback HTTP deployments could skip Host/Origin validation. This change fixes those MCP boundaries while keeping messaging permissions and quotas in fmsg-webapi.
Normal stdio setup remains an HTTPS API URL and key. Authorized conversations and automation gain no additional MCP confirmation gate; the AI host's approval settings still apply.
Changes
download_attachmentread-only with a 256 KiB default inline budget enforced while streaming. Return text attachments as text and images once. Add opt-in stdiosave_attachment, enabled byFMSG_MCP_DOWNLOAD_DIR, which streams directly to generated filenames without model-supplied paths or overwrites. Repeat saves create numbered filenames. Attachment streams use response-header and idle deadlines, allowing progressing downloads to exceed 60 seconds. The operator controls the destination folder and its ancestors; registry metadata exposes the optional setting.send()reports replacement count and transmitted topic. Bound error previews at 2 KiB while preserving canonical JSON 400/413 host-policy explanations and delivery codes.Validation
129cac1: Node 22, Node 24, Docker image build/version check, and real two-host fmsg-docker acceptance including cross-account isolation. Docker acceptance ran in CI because Docker is unavailable locally.Release and follow-up
Target the next release as 0.2.0 because the old download
save_to/saved_tocontract is removed. The release workflow continues to set package versions from the release tag and publish to npm; this PR does not publish a release.Receive pagination/cursor correctness, broader service/output budgets, practical attachment upload workflows, host compatibility testing and hosted OAuth remain tracked in
ROADMAP.md.